package com.randi.file.utils;

import com.randi.common.config.AppConfig;
import com.randi.common.enums.ResultCode;
import com.randi.common.exception.ServiceException;
import com.randi.common.utils.StringUtils;
import com.randi.file.entity.vo.FileSliceMergeVo;
import com.randi.file.entity.vo.FileSliceUploadCheckVo;
import com.randi.file.entity.vo.FileSliceUploadVo;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.math.BigInteger;
import java.nio.channels.FileChannel;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 文件上传工具类
 *
 * @author randi
 * @date 2024/10/30 14:06
 * @since 1.0.0
 */
@Component
public class UploadSliceUtils extends UploadFileUtils {

    /**
     * 分片文件保存目录
     */
    private static String FILE_SLICE_FOLDER;

    /**
     * 分片合并完成是否删除缓存分片
     */
    private static boolean FILE_SLICE_MERGE_FINISH_TEMP_CANCEL;

    /**
     * 临时保存分片文件名称与哈希值链接符
     */
    public static final String FILE_SLICE_NAME_LINK = ".";

    @Resource
    private void init(AppConfig.FileConfig.UploadFileConfig uploadFileConfig) {
        FILE_SLICE_FOLDER = uploadFileConfig.getSlicePathSuffix();
        FILE_SLICE_MERGE_FINISH_TEMP_CANCEL = uploadFileConfig.getSliceAutoClean();
    }

    /**
     * 获取分片索引
     *
     * @param file 分片文件
     * @return 分片索引
     */
    private static Integer getFileSliceIndex(File file) {
        return file.exists() ? Integer.parseInt(getSliceNameSplit(file, 0)) : -1;
    }

    /**
     * 获取分片哈希值
     *
     * @param file 分片文件
     * @return 分片哈希值
     */
    private static String getFileSliceHash(File file) {
        return file.exists() ? getSliceNameSplit(file, 1) : null;
    }

    /**
     * 获取分片名称切分信息
     *
     * @param file  分片
     * @param index 索引 [0]分片索引 [1]分片哈希值
     * @return 切分信息
     */
    private static String getSliceNameSplit(File file, Integer index) {
        String reg = "\\" + FILE_SLICE_NAME_LINK;
        return file.getName().split(reg)[index];
    }

    /**
     * 获取分片保存路径
     *
     * @param fileHash 文件哈希
     * @return 分片保存路径
     */
    public static String getFileSliceSavePath(String fileHash) {
        if (StringUtils.isEmpty(fileHash)) {
            return FILE_SLICE_FOLDER;
        }
        return FILE_SLICE_FOLDER + SEPARATOR + fileHash;
    }

    public static String getFileSliceSaveBasePath(String fileHash) {
        String sliceSavePath = getFileSliceSavePath(fileHash);
        return getBasePath(sliceSavePath);
    }

    /**
     * 获取分片保存目录对象
     *
     * @param fileHash 文件哈希
     * @return 目录对象
     */
    public static File getFileSliceSaveFolder(String fileHash) {
        String bashPath = getFileSliceSavePath(fileHash);
        return getFolder(bashPath);
    }

    /**
     * 获取已上传分片信息
     *
     * @param fileHash 文件哈希
     * @return 上传分片信息
     */
    public static FileSliceUploadCheckVo getSliceUpload(String fileHash) {
        FileSliceUploadCheckVo fileSliceUploadCheckVo = new FileSliceUploadCheckVo();
        File sliceFolder = getFileSliceSaveFolder(fileHash);

        if (sliceFolder != null) {
            // 获取断点索引
            Integer index = UploadSliceUtils.getBreakPointIndex(sliceFolder);
            fileSliceUploadCheckVo.setBreakPointIndex(index);

            // 获取已上传分片大小
            BigInteger uploadedSize = UploadSliceUtils.getUploadedSize(sliceFolder);
            fileSliceUploadCheckVo.setUploadedSize(uploadedSize);
        }
        return fileSliceUploadCheckVo;
    }

    /**
     * 获取上传断点索引
     *
     * @param folder 分片目录
     * @return 断点索引
     */
    public static Integer getBreakPointIndex(File folder) {
        Integer pointIndex = -1;
        if (folderExist(folder)) {
            AtomicInteger index = new AtomicInteger(0);
            return Arrays.stream(Objects.requireNonNull(folder.listFiles()))
                    .sorted(Comparator.comparingInt(UploadSliceUtils::getFileSliceIndex))
                    .filter(current -> UploadSliceUtils.getFileSliceIndex(current).equals(index.getAndIncrement()))
                    .map(UploadSliceUtils::getFileSliceIndex)
                    .max(Comparator.comparing(Integer::intValue))
                    .orElse(pointIndex);
        }
        return pointIndex;
    }

    /**
     * 获取已上传大小
     *
     * @param folder 分片目录
     * @return 已上传大小
     */
    public static BigInteger getUploadedSize(File folder) {
        if (folderExist(folder)) {
            AtomicInteger index = new AtomicInteger(0);
            return Arrays.stream(Objects.requireNonNull(folder.listFiles()))
                    .sorted(Comparator.comparingInt(UploadSliceUtils::getFileSliceIndex))
                    .filter(current -> UploadSliceUtils.getFileSliceIndex(current).equals(index.getAndIncrement()))
                    .map(FileUtils::sizeOfAsBigInteger)
                    .reduce(BigInteger::add)
                    .orElse(BigInteger.ZERO);
        }
        return BigInteger.ZERO;
    }

    /**
     * 前置处理
     *
     * @param bashPath 保存路径
     */
    private static void beforeHandle(String bashPath) {
        createFolder(bashPath);
    }

    /**
     * 文件分片上传
     *
     * @param fileChunk 分片文件信息
     */
    public static boolean uploadSlice(FileSliceUploadVo fileChunk) {
        String fileHash = fileChunk.getFileHash();
        String savePath = getFileSliceSavePath(fileHash);
        beforeHandle(savePath);
        try {
            Integer index = fileChunk.getIndex();
            MultipartFile chunkFile = fileChunk.getChunkFile();
            String sliceName = StringUtils.format("{}{}{}", index, FILE_SLICE_NAME_LINK, fileHash);
            String basePath = StringUtils.format("{}/{}", getBasePath(savePath), sliceName);
            RandomAccessFile randomAccessFile = new RandomAccessFile(basePath, "rw");
            randomAccessFile.write(chunkFile.getBytes());
            randomAccessFile.close();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 分片合并前置校验
     *
     * @param fileHash 文件哈希
     * @param fileSize 文件大小
     */
    private static File beforeHandleMerge(String fileHash, BigInteger fileSize) {
        File folder = getFileSliceSaveFolder(fileHash);
        if (!folderExist(folder)) {       // 文件夹是否存在校验
            throw new ServiceException(ResultCode.FILE_UPLOAD_SLICE_FAIL_MERGE_LACK);
        }
        BigInteger uploadedSize = getUploadedSize(folder);
        // 分片上传数量校验
        File[] files = folder.listFiles();
        if (files == null || files.length == 0) {
            throw new ServiceException(ResultCode.FILE_UPLOAD_SLICE_FAIL_MERGE_LACK);
        } else if (!fileSize.equals(uploadedSize)) {
            // 文件大小校验
            throw new ServiceException(ResultCode.FILE_UPLOAD_SLICE_FAIL_MERGE_LACK);
        }
        return folder;
    }

    /**
     * 合并文件的业务代码
     *
     * @param fileSliceMerge FileSliceMergeVo
     */
    public static boolean sliceMerge(FileSliceMergeVo fileSliceMerge) {
        String fileHash = fileSliceMerge.getFileHash();
        BigInteger fileSize = fileSliceMerge.getFileSize();

        // 分片前置校验
        File folder = beforeHandleMerge(fileHash, fileSize);

        // 获取分片保存路径
        String sliceSaveBasePath = getFileSliceSaveBasePath(fileHash);

        // 获取保存路径
        String saveBasePath = getSaveFileBasePath(fileHash);

        try (FileChannel fileChannel = new RandomAccessFile(saveBasePath, "rw").getChannel()) {
            String[] slicePaths = Arrays.stream(Objects.requireNonNull(folder.listFiles()))
                    .sorted(Comparator.comparingInt(UploadSliceUtils::getFileSliceIndex))
                    .map(file -> sliceSaveBasePath + SEPARATOR + file.getName())
                    .toArray(String[]::new);
            FileChannel out;
            // 文件流起始位置
            long startIndex = 0;
            for (String slicePath : slicePaths) {
                // 读取分片输入流
                out = new RandomAccessFile(slicePath, "r").getChannel();
                fileChannel.transferFrom(out, startIndex, out.size());
                startIndex += out.size();
                out.close();
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            if (FILE_SLICE_MERGE_FINISH_TEMP_CANCEL) {
                deleteFolder(folder);
            }
        }
    }
}
